#include "File.h"
#include "Console.h"

namespace CEngine
{

#define BUFFERSIZE 65536

#ifdef WINDOWS

#define WINVER 0x0601
#include <windows.h>

File::File(void)
    :   size(0),
        readPosition(0),
        writePosition(0),
        path(),
        handle(NULL),
        flags(CE_FILE_ACCESS_READ | CE_FILE_ACCESS_WRITE | CE_FILE_OPEN_EXISTING | CE_FILE_LOCK),
        writeBufferPosition(0),
        readBufferBeginning(0),
        readBufferEnding(0),
        fileIsOpen(false),
        errorCode(CE_ERROR_NONE)
{}

File::File(FilePath p, const U32 f)
    :   size(0),
        readPosition(0),
        writePosition(0),
        path(p),
        handle(NULL),
        flags(f),
        writeBufferPosition(0),
        readBufferBeginning(0),
        readBufferEnding(0),
        fileIsOpen(false),
        errorCode(CE_ERROR_NONE)
{}

File::~File(void) {
    if(fileIsOpen) {
        close();
    }
    path = NULL;
    handle = NULL;
}

File&
File::operator=(const File& rhs) {
    if(this == &rhs) {
        return (*this);
    }

    size = rhs.size;
    readPosition = rhs.readPosition;
    writePosition = rhs.writePosition;
    path = rhs.path;
    handle = rhs.handle;
    flags = rhs.flags;
    writeBufferPosition = rhs.writeBufferPosition;
    fileIsOpen = rhs.fileIsOpen;
    errorCode = rhs.errorCode;

    for (U16 i = 0; i < writeBufferPosition; ++i) {
        writeBuffer[i] = rhs.writeBuffer[i];
    }

    return (*this);
}

bool
File::open(void) {
    if(fileIsOpen) {
        errorCode = CE_ERROR_FILE_ALREADY_OPEN;
        return false;
    }

    DWORD access = 0;

    if(flags & CE_FILE_ACCESS_READ) {
        access += GENERIC_READ;
    }
    if(flags & CE_FILE_ACCESS_WRITE) {
        access += GENERIC_WRITE;
    }

    DWORD lock = 7;
    if(flags & CE_FILE_LOCK) {
        lock = 0;
    }

    DWORD creationMode = 0;

    switch(flags & 0x1C) {
        case CE_FILE_CREATE_NEW: creationMode = CREATE_NEW;
                                 break;
        case CE_FILE_CREATE_ALWAYS: creationMode = CREATE_ALWAYS;
                                    break;
        case CE_FILE_OPEN_EXISTING: creationMode = OPEN_EXISTING;
                                    break;
        case CE_FILE_OPEN_ALWAYS: creationMode = OPEN_ALWAYS;
                                  break;
        default: Console::print("%q%s must have a creation mode set to be opened!%r", Console::LWHITE + (16 * Console::LRED), path.getFileName(NULL));
                 break;
    }

    DWORD attributes = 0;

    if(flags & CE_FILE_READ_ONLY) {
        attributes += FILE_ATTRIBUTE_READONLY;
    }
    if(flags & CE_FILE_DELETE_ON_CLOSE) {
        attributes += FILE_FLAG_DELETE_ON_CLOSE | FILE_ATTRIBUTE_TEMPORARY;
    }
    if(flags & CE_FILE_DONT_BUFFER) {
        attributes += FILE_FLAG_NO_BUFFERING;
    }
    if(flags & CE_FILE_HIDDEN) {
        attributes += FILE_ATTRIBUTE_HIDDEN;
    }
    if(flags & CE_FILE_RANDOM_ACCESS) {
        attributes += FILE_FLAG_RANDOM_ACCESS;
    }
    if(flags & CE_FILE_SEQUENTIAL_ACCESS) {
        attributes += FILE_FLAG_SEQUENTIAL_SCAN;
    }
    if(flags & CE_FILE_WRITE_THROUGH) {
        attributes += FILE_FLAG_WRITE_THROUGH;
    }

    if(attributes != (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_TEMPORARY)) {
        attributes += FILE_ATTRIBUTE_NORMAL;
    }

    handle = CreateFile(path.getFullPath(NULL),
                        access,
                        lock,
                        NULL,
                        creationMode,
                        attributes,
                        NULL);
    if(handle == INVALID_HANDLE_VALUE) {
        DWORD error = GetLastError();

        switch(error) {
            case ERROR_FILE_EXISTS: errorCode = CE_ERROR_FILE_EXISTS;
                                    break;
            case ERROR_FILE_NOT_FOUND: errorCode = CE_ERROR_FILE_NOT_FOUND;
                                    break;
            case ERROR_SHARING_VIOLATION: errorCode = CE_ERROR_FILE_LOCKED;
                                    break;
            default: errorCode = CE_ERROR_OTHER;
                     break;
        }

        printSystemError(error);

        fileIsOpen = false;
        size = 0;

        return false;
    }

    calculateSize();

    readPosition = 0;
    if(creationMode == OPEN_ALWAYS || creationMode == OPEN_EXISTING) {
        writePosition = size;
    }
    else {
        writePosition = 0;
    }

    writeBuffer = new U8[64 * 1024];
    readBuffer = new U8[64 * 1024];

    errorCode = CE_ERROR_NONE;
    fileIsOpen = true;
    return true;
}

bool
File::close(void) {
    if(!fileIsOpen) {
        return true;
    }

    if(writeBufferPosition != 0) {
        U8* temp = NULL;

        write(temp, 0, true);
    }

    if(CloseHandle(handle)) {
        handle = NULL;
        errorCode = CE_ERROR_NONE;
        fileIsOpen = false;

        delete writeBuffer;
        delete readBuffer;

        return true;
    }
    errorCode = CE_ERROR_OTHER;

    printSystemError(GetLastError());

    return false;
}

U64
File::read(U8* output, const U64 length, const U8 delimiter) {
    U64 bytesRead = 0;

    LARGE_INTEGER li;
    li.QuadPart = readPosition;
    SetFilePointerEx(handle, li, NULL, FILE_BEGIN);

    if(readPosition < readBufferBeginning || readPosition > readBufferEnding || readBufferBeginning == readBufferEnding) {
        DWORD temp = 0;

        if(!ReadFile(handle, (void*)readBuffer, BUFFERSIZE, &temp, NULL)) {
            errorCode = CE_ERROR_CANNOT_READ;
            printSystemError(GetLastError());

            readBufferBeginning = 0;
            readBufferEnding = 0;

            return bytesRead;
        }

        readBufferBeginning = readPosition;
        readBufferEnding = readPosition + BUFFERSIZE;
    }

    for (U64 i = 0; i < length; ++i) {
        if(readPosition == size) {
            errorCode = CE_ERROR_EOF;

            output[i] = '\0';

            return bytesRead;
        }
        if(readPosition == readBufferEnding) {
            li.QuadPart = readPosition;
            SetFilePointerEx(handle, li, NULL, FILE_BEGIN);

            DWORD temp;
            if(!ReadFile(handle, (void*)readBuffer, BUFFERSIZE, &temp, NULL)) {
                errorCode = CE_ERROR_CANNOT_READ;
                printSystemError(GetLastError());

                readBufferBeginning = 0;
                readBufferEnding = 0;

                return bytesRead;
            }

            readBufferBeginning = readPosition;
            readBufferEnding = readPosition + BUFFERSIZE;
        }
        if(flags & CE_FILE_ASCII) {
            if(delimiter == '\0') {
                if(readBuffer[readPosition - readBufferBeginning] == '\n' || readBuffer[readPosition - readBufferBeginning] == '\r') {
                    if(readBuffer[readPosition - readBufferBeginning] == '\r' && readBuffer[(readPosition - readBufferBeginning) + 1] == '\n') {
                        output[i] = '\0';
                        ++++readPosition;

                        return bytesRead;
                    }

                    output[i] = '\0';
                    ++readPosition;

                    return bytesRead;
                }
            }
            else {
                if(readBuffer[readPosition - readBufferBeginning] == delimiter) {
                    output[i] = '\0';
                    ++readPosition;

                    return bytesRead;
                }
            }
        }

        output[i] = readBuffer[readPosition - readBufferBeginning];
        ++readPosition;
        ++bytesRead;
    }

    return bytesRead;
}

U64
File::write(U8* input, const U64 length, const bool flush) {
    U64 bytesWritten = 0;

    LARGE_INTEGER li;
    li.QuadPart = writePosition;

    SetFilePointerEx(handle, li, NULL, FILE_BEGIN);

    for (U64 i = 0; i < length; ++i) {
        if(writeBufferPosition == USHRT_MAX) {
            DWORD temp;

            if(!WriteFile(handle, (void*)writeBuffer, (DWORD)writeBufferPosition, &temp, NULL)) {
                errorCode = CE_ERROR_CANNOT_WRITE;
                printSystemError(GetLastError());

                calculateSize();

                return bytesWritten;
            }

            writePosition += writeBufferPosition;
            writeBufferPosition = 0;
        }
        if(flags & CE_FILE_ASCII && (input[i] == '\n' || (input[i] == '\r' && !input[i + 1] == '\n'))) {
            writeBuffer[writeBufferPosition] = '\r';
            writeBuffer[writeBufferPosition + 1] = '\n';
            ++++writeBufferPosition;
            ++bytesWritten;
            continue;
        }

        writeBuffer[writeBufferPosition] = input[i];
        ++writeBufferPosition;
        ++bytesWritten;
    }

    if(flush && writeBufferPosition != 0) {
        DWORD temp;

        if(!WriteFile(handle, (void*)writeBuffer, (DWORD)writeBufferPosition, &temp, NULL)) {
            errorCode = CE_ERROR_CANNOT_WRITE;
            printSystemError(GetLastError());

            calculateSize();

            return bytesWritten;
        }

        writePosition += writeBufferPosition;
        writeBufferPosition = 0;
    }

    calculateSize();

    return bytesWritten;
}

void
File::printSystemError(U32 error) {
    U32 bufferSize = 1024;
    CHAR buffer[bufferSize];

    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM,
                  NULL,
                  error,
                  0,
                  buffer,
                  bufferSize - 1,
                  NULL);
    Console::print("%q%s%r", Console::LWHITE + (16 * Console::LRED), buffer);
}

U64
File::calculateSize(void) {
    if(!GetFileSizeEx(handle, (LARGE_INTEGER*)&size)) {
        errorCode = CE_ERROR_OTHER;
        size = 0;

        printSystemError(GetLastError());
    }

    return size;
}

#endif

}
